#include <pthread.h>
#include <iostream>
#include <cstdlib>
#include <cassert>
#include <cstring> // for memcpy

#include "outputdevice.h"
#include "Device.h"
#include "common_defs.h"
#include "utils.h"

using namespace std;

// instance of the output device
static Device dev;

static void print_err_msg( result_t result )
{
	switch( result ) {

	case ERROR_UNKNOWN_REQUEST:				// fall through
	case ERROR_DEVICE_HASNT_INITIALIZED:	// fall through
	case ERROR_DEVICE_ALREADY_INITIALIZED:	// fall through
	case ERROR_DEVICE_CLOSED:
		cerr<< "Output device library error" << endl;
		break;

	case ERROR_SYSTEM_CALL:					// fall through
	case ERROR_IO:
		cerr<< "system error" << endl;
		break;

	default:
		break;

	}
}

/*
 * this function make sure we create the device only once (it may be initialized a
 * couple of times but only created once).
 */
static result_t first_init()
{
	// this lock won't be destroyed on termination
	static pthread_mutex_t init_lock = PTHREAD_MUTEX_INITIALIZER;
	static bool device_created = false; // indicate if the device was already created once

	result_t result = OPERATION_SUCCESS;

	/*
	 * on the first time we need to create the device - instead of doing it on the
	 * constructor we do it during the first call to the initialization, this way we able
	 * to retrieve system errors (ex's requirement) which we couldn't do it from
	 * constructor that invoked for static instance.
	 *
	 */
	if ( pthread_mutex_lock( & init_lock ) )
	{
		result = ERROR_SYSTEM_CALL;

		goto end;
	}

	if ( ! device_created )
	{
		result = dev.create_device();

		if ( OPERATION_SUCCESS == result )
		{
			device_created = true;
		}
	}

	if ( pthread_mutex_unlock( & init_lock ) )
	{
		result = ERROR_SYSTEM_CALL;
	}

end:

	return result;

}

int initdevice(char *filename)
{
	result_t result;

	if ( NULL == filename )
	{
		cerr<< "Output device library error" << endl;
		return EXIT_ERROR;
	}

	// check if the device need to be created for the first time
	result = first_init();
	if ( OPERATION_SUCCESS != result  )
	{
		print_err_msg( result );
		return EXIT_ERROR;
	}

	result = dev.init_device( filename );
	if ( OPERATION_SUCCESS == result )
	{
		return EXIT_SUCCESS;
	}

	// there was an error during initialization
	print_err_msg( result );

	switch ( result ) {

	case ERROR_IO:
		return EXIT_ERROR_FILESYSTEM;

	default:
		return EXIT_ERROR;
	}
}

int write2device(char *buffer, int length)
{
	result_t result;
	uint id;
	char *local_buf = NULL;

	if ( NULL == buffer || length < 0 )
	{
		cerr<< "Output device library error" << endl;
		return EXIT_ERROR;
	}

	// copy the buffer to local buffer
	local_buf = (char *) malloc( length );
	if ( NULL == local_buf )
	{
		ERR_PRINT("allocation of request buffer was failed!\n");
		cerr<< "system error" << endl;
		return EXIT_ERROR;
	}
	memcpy( local_buf, buffer, length );

	// create a new request in the device
	result = dev.add_request( local_buf, length, & id );

	if ( OPERATION_SUCCESS != result )
	{
		print_err_msg( result );

		return EXIT_ERROR;
	}

	// every thing works fine - return the request ID
	return (int)id;
}

void flush2device(int task_id)
{
	result_t result;

	if ( task_id < 0 )
	{
		result = ERROR_UNKNOWN_REQUEST;
		goto error;
	}

	result = dev.wait_request( task_id );
	if ( OPERATION_SUCCESS == result )
	{
		return;
	}

error:

	print_err_msg( result );

	// forced closing of the device
	dev.device_release_resources(true);

	exit( EXIT_ERROR );
}

int wasItWritten(int task_id)
{
	result_t result;

	if ( task_id < 0 )
	{
		// the given request is non-exists (invalid)
		cerr<< "Output device library error" << endl;
		return -2;
	}


	result = dev.was_writen( task_id );

	print_err_msg( result );

	switch ( result ) {

	case ERROR_DEVICE_HASNT_INITIALIZED:	// fall through
	case ERROR_SYSTEM_CALL:
		return EXIT_ERROR;

	case ERROR_UNKNOWN_REQUEST:
		return -2;

	case REQUEST_WAS_WRITEN:
		return 0;

	case REQUEST_WASNT_WRITEN:
		return 1;

	default:
		assert(0);
		break;

	}

	// should never be here
	return EXIT_ERROR;
}

void closedevice()
{
	result_t result = dev.device_stop();

	if ( OPERATION_SUCCESS == result )
	{
		return;
	}

	// there was an error
	print_err_msg( result );

	// forced release of device's resources
	dev.device_release_resources(true);

	exit( EXIT_ERROR );
}
